package com.iflytek.jzcpx.procuracy.card.config;

import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.iflytek.jzcpx.procuracy.tools.elle.ElleClient;
import com.iflytek.jzcpx.procuracy.tools.elle.ElleTextTypeEnum;
import com.iflytek.jzcpx.procuracy.tools.ocr.OcrClient;
import com.iflytek.sxs.dfs.client.FdfsClient;
import org.apache.commons.collections4.MapUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.csource.common.MyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

/**
 * 案卡功能模块配置
 *
 * @author <a href=mailto:ktyi@iflytek.com>伊开堂</a>
 * @date 2019/8/20 14:43
 */
@Configuration
@ConditionalOnClass(PropertyResolver.class)
@EnableConfigurationProperties({OcrServerProps.class, ElleServerProps.class})
public class CardConfig {
    private static final Logger logger = LoggerFactory.getLogger(CardConfig.class);

    @Autowired
    private OcrServerProps ocrProps;

    @Autowired
    private ElleServerProps elleProps;

    @Value("${fastdfs.server.track}")
    private String trackServer;

    @Bean
    @ConditionalOnMissingBean(FdfsClient.class)
    public FdfsClient fdfsClient() throws IOException, MyException {
        FdfsClient fdfsClient = new FdfsClient();
        fdfsClient.setTrackerServer(trackServer);
        fdfsClient.init();
        return fdfsClient;
    }

    @Bean
    @ConditionalOnMissingBean(OcrClient.class)
    public OcrClient buildOcrClient() {
        // 创建 ocr 客户端
        return new OcrClient(ocrProps.getServer(), ocrProps.getPath(), ocrProps.getCoreSize(), ocrProps.getRetry());
    }

    @Bean
    public ElleClient buildElleClient() {
        // 创建 elle 客户端
        return new ElleClient(elleProps.getServer(), elleProps.getPath());
    }

    /** 文书模板编码配置 */
    @Bean
    public WsmbbmConfig buildWsmbbmConfig() {
        WsmbbmConfig config = new WsmbbmConfig();
        // 正式环境下运行时从data目录读取
        String filePath = "./data/wsmbbm.xlsx";
        FileSystemResource wsmbResource = new FileSystemResource(filePath);

        // 开发阶段在ide中启动项目时从resources目录下读取
        ClassPathResource classPathResource = new ClassPathResource("wsmbbm.xlsx");

        if (!wsmbResource.exists() && !classPathResource.exists()) {
            logger.error("无法加载到文书模板编码配置文件: " + filePath);
            return config;
        }

        Resource resource = wsmbResource.exists() ? wsmbResource : classPathResource;
        try (InputStream is = resource.getInputStream()) {
            Workbook wsmbWB = WorkbookFactory.create(is);
            Sheet sheet0 = wsmbWB.getSheetAt(0);
            int wsmbbhIndex = parseCellIndex(sheet0, "wsmbbh");
            int elleCodeIndex = parseCellIndex(sheet0, "ellecode");
            if (wsmbbhIndex == -1 || elleCodeIndex == -1) {
                throw new RuntimeException("无法从" + filePath + "文件的第一个工作簿的第一行读取到表头：wsmbbh 或 ellecode，请检查文件内容");
            }

            for (Row row : sheet0) {
                if (row.getRowNum() == 0) {
                    // 跳过表头
                    continue;
                }

                Cell elleCodeCell = row.getCell(elleCodeIndex);
                if (elleCodeCell == null) {
                    // 未配置对应的elle 文书类型
                    continue;
                }

                String elleCode = parseCellStringVal(elleCodeCell);
                String wsmbbm = parseCellStringVal(row.getCell(wsmbbhIndex));
                ElleTextTypeEnum elleTextTypeEnum = ElleTextTypeEnum.fromName(elleCode);
                if (elleTextTypeEnum == null) {
                    logger.warn("文书模板编码配置文件第{}行的ellecode内容：{} 配置错误，请检查单元格内容。",
                            row.getRowNum(), elleCode);
                    continue;
                }

                config.addConfig(wsmbbm, elleTextTypeEnum);
            }
        }
        catch (Exception e) {
            logger.error("加载文书模板编码配置文件: ./data/wsmbbm.xlsx发生异常", e);
        }

        return config;
    }

    private int parseCellIndex(Sheet sheet, String cellName) {
        Iterator<Cell> cellIterator = sheet.getRow(0).cellIterator();
        while (cellIterator.hasNext()) {
            Cell cell = cellIterator.next();
            if (parseCellStringVal(cell).equalsIgnoreCase(cellName)) {
                return cell.getColumnIndex();
            }
        }
        return -1;
    }

    /**
     * 返回字段类别名称及其下字段文本值与编码值对应关系
     * 例如:
     * <pre>
     * 性别:
     *      男性: 9909180000001
     *      女性: 9909180000002
     * 受教育状况:
     *      中专: 9915180600006
     *      高职: 9915180600007
     *      专科毕业: 9915180600008
     *      大学本科: 9915180600009
     * </pre>
     *
     * @return
     */
    @Bean
    public ZdbmConfig buildZdbmConfig() {
        ZdbmConfig config = new ZdbmConfig();
        // 类别名称及类别编码
        ClassPathResource defaultFldmlb = new ClassPathResource("fldmlb1.xlsx");
        // 字段名称与字段编码值
        ClassPathResource defaultFldm = new ClassPathResource("fldm1.xlsx");

        // 先加载jar包内的默认配置
        loadFromInnerJar(config, defaultFldmlb, defaultFldm);

        // 再从 data 目录加载最新配置并覆盖默认配置
        FileSystemResource runtimeFldmlb = new FileSystemResource("./data/fldmlb.xlsx");
        FileSystemResource runtimeFLDM = new FileSystemResource("./data/fldm.xlsx");
        if (runtimeFldmlb.exists() && runtimeFLDM.exists()) {

        }

        // 单位结构代码
        Map<String, String> jgbmConfig = buildJgbmConfig();
        config.add("机构/单位", jgbmConfig);

        return config;
    }

    private void loadFromInnerJar(ZdbmConfig config, ClassPathResource defaultFldmlb, ClassPathResource defaultFldm) {
        String path = null;
        try {
            path = defaultFldmlb.getURL().getPath();
        }
        catch (IOException e) {
            path = defaultFldmlb.toString();
        }
        logger.info("开始从jar中加载案卡字段代码配置文件, {}", path);

        try (InputStream fldmlbInputStream = defaultFldmlb.getInputStream();
                InputStream fldmInputStream = defaultFldm.getInputStream()) {
            // 类别名称 -> 类别编码
            Map<String, String> fldmlbMap = parseFldmlb(fldmlbInputStream);
            // 类别编码 -> (名称 -> 编码)
            Map<String, Map<String, String>> fldmMap = parseFldm(fldmInputStream);

            for (Map.Entry<String, String> fldmlbEntry : fldmlbMap.entrySet()) {
                String lbmc = fldmlbEntry.getKey();
                String lbbm = fldmlbEntry.getValue();
                // 类别名称 -> (名称 -> 编码)
                Map<String, String> lbbmMap = fldmMap.get(lbbm);
                if (MapUtils.isNotEmpty(lbbmMap)) {
                    config.add(lbmc, lbbmMap);
                }
            }
        }
        catch (Exception e) {
            logger.error("加载案卡默认字段代码配置文件异常.", e);
        }
    }

    /** 解析分类代码， 类别编码：（分类名称：分类代码）， 如：9909:(男:9909180000001,女:9909180000002) */
    private Map<String, Map<String, String>> parseFldm(InputStream fldmInputStream) throws IOException {
        Workbook fldmWB = WorkbookFactory.create(fldmInputStream);
        Sheet sheet0 = fldmWB.getSheetAt(0);
        // 根据表头确定需要字段的下标
        int lbbmIndex = parseCellIndex(sheet0, "LBBM");
        int dmIndex = parseCellIndex(sheet0, "DM");
        int mcIndex = parseCellIndex(sheet0, "MC");
        if (lbbmIndex == -1 || dmIndex == -1 || mcIndex == -1) {
            throw new RuntimeException("无法从fldm.xlsx文件的第一个工作簿的第一行读取到表头：LBBM，DM 或 MC，请检查文件内容");
        }

        Map<String, Map<String, String>> dataMap = new HashMap<>();
        for (Row row : sheet0) {

            // 类别编码
            String lbbm = parseCellStringVal(row.getCell(lbbmIndex));
            Map<String, String> lbbmMap = dataMap.computeIfAbsent(lbbm, k -> new HashMap<>());

            // 名称
            Cell mcCell = row.getCell(mcIndex);
            // 代码
            Cell dmCell = row.getCell(dmIndex);
			/*
			 * String dm=parseCellStringVal(dmCell); if("9903105020000".equals(dm)) {
			 * System.out.println(); }
			 */
            lbbmMap.put(parseCellStringVal(mcCell), parseCellStringVal(dmCell));
        }

        return dataMap;
    }

    /** 解析分类代码类别信息， 类别名称 ：类别编码，如：性别:9909 */
    private Map<String, String> parseFldmlb(InputStream fldmlbInputStream) throws IOException {
        Workbook fldmlbWB = WorkbookFactory.create(fldmlbInputStream);
        Sheet sheet0 = fldmlbWB.getSheetAt(0);

        // 读取表头确定字段下表
        int lbbmIndex = parseCellIndex(sheet0, "LBBM");
        int lbmcIndex = parseCellIndex(sheet0, "LBMC");
        if (lbbmIndex == -1 || lbmcIndex == -1) {
            throw new RuntimeException("无法从fldmlb.xlsx文件的第一个工作簿的第一行读取到表头：LBBM，LBMC 请检查文件内容");
        }

        Map<String, String> dataMap = new HashMap<>();
        Iterator<Row> rowIterator = sheet0.rowIterator();
        while (rowIterator.hasNext()) {
            Row row = rowIterator.next();
            // 类别编码
            Cell lbbmCell = row.getCell(lbbmIndex);
            // 类别名称
            Cell lbmcCell = row.getCell(lbmcIndex);
            dataMap.put(lbmcCell.getStringCellValue(), parseCellStringVal(lbbmCell));
        }
        return dataMap;
    }

    public Map<String, String> buildJgbmConfig() {
        Map<String, String> dataMap = new HashMap<>();
        // 正式环境下运行时从data目录读取
        String filePath = "./data/jgdm.xlsx";
        FileSystemResource fileSystemResource = new FileSystemResource(filePath);

        // 开发阶段在ide中启动项目时从resources目录下读取
        ClassPathResource classPathResource = new ClassPathResource("jgdm.xlsx");

        if (!fileSystemResource.exists() && !classPathResource.exists()) {
            logger.error("无法加载到机构代码配置文件: " + filePath);
            return dataMap;
        }

        Resource resource = fileSystemResource.exists() ? fileSystemResource : classPathResource;
        try {
            logger.info("加载机构代码配置, {}", resource.getFile().getAbsolutePath());
        }
        catch (Exception e) {
            //
        }

        try (InputStream is = resource.getInputStream()) {
            Workbook wsmbWB = WorkbookFactory.create(is);
            Sheet sheet0 = wsmbWB.getSheetAt(0);
            int dmIndex = parseCellIndex(sheet0, "dm");
            int mcIndex = parseCellIndex(sheet0, "mc");
            if (dmIndex == -1 || mcIndex == -1) {
                throw new RuntimeException("无法从" + filePath + "文件的第一个工作簿的第一行读取到表头：dm 或 mc，请检查文件内容");
            }

            for (Row row : sheet0) {
                if (row.getRowNum() == 0) {
                    // 跳过表头
                    continue;
                }

                Cell mcCell = row.getCell(mcIndex);
                if (mcCell == null) {
                    continue;
                }

                String mc = parseCellStringVal(mcCell);
                String dm = parseCellStringVal(row.getCell(dmIndex));

                dataMap.put(mc, dm);
            }
        }
        catch (Exception e) {
            logger.error("加载机构代码配置文件: ./data/jgdm.xlsx发生异常", e);
        }

        logger.info("机构代码条数: {}", MapUtils.size(dataMap));
        return dataMap;
    }
    private String parseCellStringVal(Cell cell) {
        switch (cell.getCellType()) {
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    Date cellValue = cell.getDateCellValue();
                    return String.valueOf(cellValue.getTime());
                }
                else {
                    DecimalFormat df = new DecimalFormat("0");
                    return df.format(cell.getNumericCellValue());
                }
            case STRING:
                return cell.getRichStringCellValue().getString();
            case FORMULA:
                return cell.getCellFormula();
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case ERROR:
                return String.valueOf(cell.getErrorCellValue());
            default:
                return cell.getStringCellValue();
        }
    }
}
